#include "WindowClass.h"

/*******************************************************************************
  Static Variable Initilizations
*******************************************************************************/
Window::WndMap Window::wndControl;
Window::WndSet Window::wndRegistry;
int Window::numWindows = 0;
bool Window::isQuiting = false;

// Previous Screen Settings
static DEVMODE  DMsaved;
static DEVMODE* DMsavedPtr = NULL;

#include <iostream>

/*******************************************************************************
  Message Handling
*******************************************************************************/
LRESULT CALLBACK Window::MessageRouter( HWND hwnd, UINT msg, WPARAM wparam,
                                        LPARAM lparam ) {
  Window* win = NULL;

  // Determine if the window has been created
  WndIter wIt = wndControl.find( hwnd );
  if( wIt != wndControl.end() ) {
    // Window was found, retrieve the pointer
    win = wIt->second;
  }
  else {
    // Check if the window is registered ( should have been at creation )
    WSIter wlIt = 
      wndRegistry.find( (Window*)(((LPCREATESTRUCT)lparam)->lpCreateParams) );

    if( wlIt != wndRegistry.end() ) {
      // Add the window to the contact list with its HWND, then remove it
      // from the registry.
      win = *wlIt;
      AddWndContact( hwnd, win );
      RemoveWndPtr( wlIt );
    }
  }

  // If the window was found
  if( win ) {
    // Locate the procedure associated with the message for the given window
    MsgIter mIt;
    mIt = win->GetMsgProc( msg );
    if( mIt != win->GetMsgEnd() ) {
      // If the procdure exist, call the procedure
      return (mIt->second)( (*win), (long)wparam, (long)lparam );
    }
  }

  // Fall-out catch to default windows procedure
  return DefWindowProc(hwnd, msg, wparam, lparam);
}

bool Window::HandleMsgs() {
  static MSG msg;

  if( !IsActive() ) {
    return false;
  }

  if( IsQuiting() ) {
    NotifyWndClose();
  }

  if( usePeek ? ::PeekMessage( &msg, hWnd, 0, 0, PM_REMOVE) :
                ::GetMessage( &msg, hWnd, 0, 0 ) ) {
    //if( msg.message == WM_PAINT ) {
    //  ::TranslateMessage( &msg );
    //  ::DispatchMessage( &msg );
    //  return false;
    //}

    ::TranslateMessage( &msg );
    ::DispatchMessage( &msg );

    if( useWait ) {
      ::WaitMessage();
    }
    return true;
  }
  
  return false;
}

Window::MsgIter Window::GetMsgProc( UINT message ) {
  // Determine if the procedure exist
  return msgControl.find( message ); // Return the pointer to the procedure
}

Window::MsgIter Window::GetMsgEnd() {
  return msgControl.end();
}

Window::MsgProc Window::RegisterProc( UINT message, MsgProc func ) {
  MsgIter it = msgControl.find( message );
  if( it != msgControl.end() ) {
    MsgProc prev = it->second; // Stores previous procedure
    it->second = func; // Sets the new procedure
    return prev;
  }
  // Inserts the procedure into the empty location
  msgControl.insert( std::pair<UINT, MsgProc>(message, func) );

  return NULL; //returns previous procedure
}

Window::MsgProc Window::RegisterIfNoProc( UINT message, MsgProc func ) {
  MsgIter it = msgControl.find( message );
  if( it == msgControl.end() ) {
    msgControl.insert( std::pair<UINT, MsgProc>(message,func) );
    return NULL;
  }
  return func;
}

Window::MsgProc Window::ClearProc( UINT message ) {
  MsgProc prev = NULL;
  MsgIter it = msgControl.find( message );
  if( it != msgControl.end() ) {
    prev = it->second;
    msgControl.erase( it );
  }
  return prev;
}

void Window::ClearAllProc() {
  msgControl.clear();
}

/*******************************************************************************
  Window Handling
*******************************************************************************/
Window::Window() :  hWnd(NULL),
                    hInst(DEF_INSTANCE),
                    height(DEF_HEIGHT),
                    width(DEF_WIDTH),
                    posX(DEF_POS_X),
                    posY(DEF_POS_Y),
                    classStyle(DEF_CLASS_STYLE),
                    wndStyle(DEF_WND_STYLE),
                    icon(DEF_ICON),
                    cursor(DEF_CURSOR),
                    background(DEF_BACKGROUND),
                    currentShowWnd(DEF_WND_SHOW),
                    usePeek(DEF_USE_PEEK),
                    useWait(DEF_USE_WAIT),
                    isActive(false),
                    isFullScreen(DEF_IS_FULLSCREEN),
                    usingFullScreen(false),
                    titleName(DEF_TITLE) {                    
  // Save the current display state, only once at start up
  if( !DMsavedPtr ) {
    DMsavedPtr = &DMsaved;
    EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, DMsavedPtr);
  }

  ClearAllProc();
  AddWndCount();
}

Window::~Window() {
  RemoveWndCount();
}

bool Window::Create() {
  if( IsQuiting() || IsActive() ) {
    return false;
  }
  
  WNDCLASS wc = GenerateWndClass();
  RegisterWndClass( wc );

  return CreateWnd();
}

void Window::Destroy() {
  if( IsActive() ) {
    DestroyWnd();
    UnregisterWndClass();
  }
}

/*******************************************************************************
  Sets
*******************************************************************************/
void Window::UpdateSettings() {
  Destroy();
  Create();
  ShowWnd();
}

bool Window::CreateWnd() {
  RECT rect = GenerateWndRect();
  SetActive( true );
  AddWndPtr();

  RegisterIfNoProc( WM_CLOSE, Window::DefWndClose );
  RegisterIfNoProc( WM_SIZE, Window::DefWndSize );
  RegisterIfNoProc( WM_MOVE, Window::DefWndMove );
  RegisterIfNoProc( WM_ACTIVATE, Window::DefWndActivate );
  
  DWORD winStyle = wndStyle;
  UINT newX = posX;
  UINT newY = posY;
  if( IsFullScreen() ) {
    winStyle = WS_POPUP;
    newX = 0;
    newY = 0;
  }

  if( !( ::CreateWindow( className.c_str(),
                       titleName.c_str(),
                       winStyle,
                       newX, newY,
                       rect.right - rect.left,
                       rect.bottom - rect.top,
                       (HWND)NULL,
                       (HMENU)NULL,
                       hInst,
                       (LPVOID)this ) ) )
  {
    MessageBox( NULL, "Failed To Create Window.", "ERROR",
                MB_OK | MB_ICONEXCLAMATION );
    RemoveWndPtr();
    SetActive( false );
    return false;
  }
  return true;
}

void Window::DestroyWnd() {
  SetActive( false );
  DestroyWindow( hWnd );
  RemoveWndContact();
}

void Window::RegisterWndClass( WNDCLASS& wc ) {
  if ( !RegisterClass( &wc ) )
  {
    MessageBox( NULL, "Failed To Register The Window Class.", "ERROR",
                MB_OK | MB_ICONEXCLAMATION );
  }
}

void Window::UnregisterWndClass() {
  if ( !UnregisterClass( className.c_str(), hInst ) )
  {
    MessageBox( NULL, "Failed To Unregister The Window Class.", "ERROR",
                MB_OK | MB_ICONEXCLAMATION );
  }
}


RECT Window::GenerateWndRect() {
  RECT rect;
  rect.left   = 0;
  rect.right  = width;
  rect.top    = 0;
  rect.bottom = height;
  
  DWORD winStyle = IsFullScreen() ? WS_POPUP : wndStyle;
  
  AdjustWindowRect( &rect, winStyle, false );

  return rect;
}

WNDCLASS Window::GenerateWndClass() {
  WNDCLASS wc;
  wc.style         = classStyle;
  wc.lpfnWndProc   = MessageRouter;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInst;
  wc.hIcon         = LoadIcon( NULL, icon );
  wc.hCursor       = LoadCursor( NULL, cursor );
  wc.hbrBackground = background;
  wc.lpszMenuName  = menuName.c_str();

  GenerateClassName();
  wc.lpszClassName = className.c_str();

  return wc;
}

void Window::GenerateClassName() {
  static int append = 0;
  className = "WindowClass.";
  className += ToString( append );
  ++append;
}

void Window::ShowWnd( UINT style ) {
  UINT tempWidth = width;
  UINT tempHeight = height;
  
  if( IsActive() ) {
    currentShowWnd = style;
    ::ShowWindow( hWnd, style );
  }
  ::SendMessage( hWnd, WM_SIZE, SIZE_RESTORED, MAKELPARAM( tempWidth, tempHeight ) );
}

void Window::ShowWnd() {
  ShowWnd( currentShowWnd );
}

void Window::SetForegroundWnd() {
  if( IsActive() ) {
    ::SetForegroundWindow( hWnd );
  }
}

void Window::SetFocus() {
  if( IsActive() ) {
    ::SetFocus( hWnd );
  }
}

void Window::NotifyAppQuit() {
  SetQuit( true );
  PostQuitMessage(0);
}

void Window::NotifyWndClose() {
  PostMessage( hWnd, WM_CLOSE, 0, 0 );
}

void Window::AddWndContact( HWND hwnd, Window* win ) {
  wndControl.insert( std::pair<HWND,Window*>( hwnd, win ) );
  win->SetHwnd( hwnd );
}

void Window::RemoveWndContact() {
  WndIter it;
  it = wndControl.find( hWnd );

  if( it != wndControl.end() ) {
    wndControl.erase( it );
  }
}

void Window::AddWndPtr() {
  wndRegistry.insert( this );
};

void Window::RemoveWndPtr() {
  WSIter iter = wndRegistry.find( this );
  if( iter != wndRegistry.end() )
    wndRegistry.erase( iter );
};

void Window::RemoveWndPtr( WSIter& iter ) {
  wndRegistry.erase( iter );
};

void Window::FullScreenMode() {
  if( !usingFullScreen ) {
    DEVMODE dmScreenSettings;         // Device Mode
      memset(&dmScreenSettings,0,sizeof(dmScreenSettings));   // Makes Sure Memory's Cleared
      dmScreenSettings.dmSize=sizeof(dmScreenSettings);   // Size Of The Devmode Structure
      dmScreenSettings.dmPelsWidth  = GetWidth();     // Selected Screen Width
      dmScreenSettings.dmPelsHeight = GetHeight();      // Selected Screen Height
      dmScreenSettings.dmBitsPerPel = 32;       // Selected Bits Per Pixel
      dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

    if( ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN ) !=
        DISP_CHANGE_SUCCESSFUL )
    {
      usingFullScreen = false;
      SetFullScreen( false );
      UpdateSettings();
      return;
    }
    usingFullScreen = true;
  }
}

void Window::WindowMode() {   
  if( usingFullScreen ) {
    if (!ChangeDisplaySettings(NULL,CDS_TEST)) {    // If The Shortcut Doesn't Work ( NEW )
      ChangeDisplaySettings(NULL,CDS_RESET);    // Do It Anyway (To Get The Values Out Of The Registry) ( NEW )
      ChangeDisplaySettings(DMsavedPtr,CDS_RESET);  // Change It To The Saved Settings ( NEW )
    } 
    else {
      ChangeDisplaySettings(NULL,CDS_RESET);    // If It Works, Go Right Ahead ( NEW )
    }
    usingFullScreen = false;
  }
}

/*******************************************************************************
  Defualt Procedures
*******************************************************************************/
long Window::DefWndClose( Window& win, long wparam, long lparam ) {
  win.Destroy();
  return 0;
}

long Window::DefWndSize( Window& win, long wparam, long lparam ) {
  win.SetSize( HIWORD(lparam), LOWORD(lparam) );
  return 0;
}

long Window::DefWndMove( Window& win, long wparam, long lparam ) {
  if( !win.IsFullScreen() ) {
    RECT rect;
    ::GetWindowRect( win.GetHwnd(), &rect );
    win.SetPos( rect.left, rect.top );
  }
  return 0;
}

long Window::DefWndActivate( Window& win, long wparam, long lparam ) {
  switch( LOWORD(wparam) ) {
    case WA_CLICKACTIVE:
    case WA_ACTIVE:
      if( win.IsFullScreen() ) {
        win.ShowWnd( SW_RESTORE );
        win.FullScreenMode();
      }
      else {
        win.ShowWnd();
        win.WindowMode();
      }
      win.SetFocus();
      break;
    case WA_INACTIVE:
      if( win.IsFullScreen() ) {
        win.WindowMode();
        win.ShowWnd( SW_SHOWMINIMIZED );
      }
      break;
  }
  return 0;
}
